package com.interactive1.utils;

import java.io.*;

/**
 * Utility class with several public static methods which return the string name of
 * of a caller method a given number of levels above.
 * <p>
 * The example using StackMethodExtractor: <br><pre>
 *
 *     class SomeClass {
 *         public void method {
 *             System.out.println("I am " + CallerMethodExtractor.getCaller(0));
 *             System.out.println(StackMethodExtractor.getCaller(1) + " just called me.");
 *         }
 *     }
 * </pre></br>
 * 
 * @author Arsen Torbarina, Interactive1 Zagreb, Ltd.
 * @version v1.11
 */
public class MethodCallerExtractor {

	public static int depthOffset = 0;
	
    /**
    * Class version info.
    */
    public static final String _VERSION = "v1.12";
    /**
     * Extracts the full name of a method above the calling method.
	 * <p>A sample of a returned value:
	 * <pre>com.interactive1.utils.MethodCallerExtractor.getStack</pre>
	 *
	 * @param level	is the index of the method that many stack levels above the
     *				calling method. The value 0 returns the name of the calling
	 *				method itself, 1 returns its immediate caller, 2 the caller
	 *				of that caller, etc.
     * @return	full name of the method, or null if it could not be found or the
	 *			given level was deeper than the actual number of calls.
     */
    public static String getCaller(int level) {
		try {
			String stack = getStack();
			int linestart = linestart(stack, level);
			return stack.substring(linestart+5, stack.indexOf("(", linestart+5));
		} catch (Exception e) {
			return null;
		}
    }

	/**
	 * Extracts the full name of the class above the calling method.
	 * <p>A sample of a returned value:
	 * <pre>com.interactive1.utils.MethodCallerExtractor</pre>
	 *
	 * @param level	is the index of the method that many stack levels above the
	 *				calling method. The value 0 returns the class of the calling
	 *				method itself, 1 returns its immediate caller, 2 the caller
	 *				of that caller, etc.
	 * @return	full name of the class, or null if it could not be found or the
	 *			given level was deeper than the actual number of calls.
	 */
	public static String getCallerClass(int level) {
		try {
			String stack = getStack();
			int linestart = linestart(stack, level);
			String m = stack.substring(linestart+5, stack.indexOf("(", linestart+5));
			return m.substring(0, m.lastIndexOf("."));
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * Extracts the source file name and a line number
	 * where the called occured from, of a method above the calling method.
	 *
	 * <p>A sample of a returned value:
	 * <pre>MethodCallerExtractor.java:96</pre>
	 *
	 * @param level	is the index of the method that many stack levels above the
	 *				calling method. The value 0 returns the name of the calling
	 *				method itself, 1 returns its immediate caller, 2 the caller
	 *				of that caller, etc.
	 * @return	source file name and a line number of the method,
	 *			or null if it could not be found or the	given level was deeper
	 *			than the actual number of calls.
	 */
	public static String getSourceCaller(int level) {
		try {
			String stack = getStack();
			int linestart = linestart(stack, level);
			return stack.substring(stack.indexOf("(", linestart+5)+1, stack.indexOf(")", linestart+5));
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * Extracts the full name, its source file name and a line number
	 * where the called occured from, of a method above the calling method.
	 *
	 * <p>A sample of a returned value:
	 * <pre>com.interactive1.utils.MethodCallerExtractor.getStack(MethodCallerExtractor.java:96)</pre>
	 *
	 * @param level	is the index of the method that many stack levels above the
	 *				calling method. The value 0 returns the name of the calling
	 *				method itself, 1 returns its immediate caller, 2 the caller
	 *				of that caller, etc.
	 * @return	full name, its source file name and a line number of the method,
	 *			or null if it could not be found or the	given level was deeper
	 *			than the actual number of calls.
	 */
	public static String getFullCaller(int level) {
		try {
			String stack = getStack();
			int linestart = linestart(stack, level);
			return stack.substring(linestart+5, stack.indexOf(")", linestart+5)+1);
		} catch (Exception e) {
			return null;
		}
	}

    /**
     * Returns the full stack trace of calls, starting with this method.
     *
     * @return	full stack trace beginning with the line "<code>java.lang.Throwable</code>".
     */
    public static String getStack() {
        StringWriter sw = new StringWriter();
		new Throwable().fillInStackTrace().printStackTrace(new PrintWriter(sw));
		return sw.toString();    	
    }

	/**
	 * Returns the index within the stack string, where data for the given
	 * level starts.
	 *
	 * @param stack full stack trace, as returned by <code>getStack()</code>.
	 * @param level	is the index of the method that many stack levels above the
	 *				calling method. The value 0 refers to the calling method itself,
	 *				1 refers to its immediate caller, 2 to the caller
	 *				of that caller, etc.
	 * @return index within the stack string or -1 if not found
	 * @throws java.lang.NullPointerException if the level was too deep
	 * @see #getStack
	 */
	public static int linestart(String stack, int level) {
/*		int stackBase = level + 2;
		int linestart = -1;
*/
		int stackBase = level+1+depthOffset;
		int linestart = stack.lastIndexOf("MethodCallerExtractor.getStack(");

		for(int i=0; i<stackBase && linestart>=0; i++) {
			linestart = stack.indexOf("\n", linestart+1);
		}
		if (linestart<0) throw new NullPointerException();
		return linestart;	
	}
	public static void main(String[] args) {
		for (int i=0;i<10;i++)
			System.out.println(i+" "+getCaller(i));
	}
}
